local ffi = require "ffi"
local bit = require "bit"

pcall(function ()
    ffi.cdef[[
    void SHA1(unsigned char * t, int len, unsigned char * output_string);
    void EVP_EncodeBlock(unsigned char * t, const unsigned char * f, int len);
    ]]
end)

local ssl
if "Windows" == jit.os then
    ssl = ffi.load("libeay32")
else
    ssl = ffi.load("libssl")
end

local sha1 = function (data)
	local SHA_DIGEST_LENGTH = 20
	local output_string = ffi.new("unsigned char[?]", SHA_DIGEST_LENGTH + 1)
	ssl.SHA1(ffi.cast("unsigned char *", data), string.len(data), output_string)
	local rsl_len = (((string.len(ffi.string(output_string))+2)/3)*4) + 1 --多一个字符
	local result = ffi.new("unsigned char[?]", rsl_len)
	ssl.EVP_EncodeBlock(result, output_string, string.len(ffi.string(output_string)))
	return ffi.string(result)
end

--数据流萃取器
--extract：提取方法，参数：待提取数据的trunk，返回值：提取数据后余下数据的trunk，如果返回的和传入的相同，就不再提取了
local StreamExtractor = function (extract)
    local data = ""
    local onTrunk = function (trunk)
        if not trunk then
            return
        end
        data = data .. trunk
        while true do
            local oldData = data
            local newData = extract(oldData)
            if oldData == newData then
                break
            else
                data = newData
            end
        end
    end
    return onTrunk
end
--获取某个字符char的第index位，startIndex和endIndex最大为8
local getBit = function (char, startIndex, endIndex)
    if not endIndex then
        endIndex = startIndex
    end
    local tmp = string.byte(char)
    tmp = bit.rshift(tmp, 8-endIndex)
    tmp = bit.band(tmp, math.pow(2, endIndex+1-startIndex)-1)
    return tmp
end
--把数字转换成字符串型
local intToStr = function (num)
    local max = 256
    local chs = {}
    local left = num
    while (0 < left) do
        local mod = left % max
        table.insert(chs, string.char(mod))
        left = math.floor(left/max)
    end
    return string.reverse(table.concat(chs))
end
--把字符串代表的数字转整型
local strToInt = function (str)
    local exponential = function (num, len)
        for index=1, len, 1 do
            num = num * 256
        end
        return num
    end
    local all = 0
    local count = string.len(str)
    for index=1, count, 1 do
        local ch = string.sub(str, index, index)
        all = all + exponential(string.byte(ch), count-index)
    end
    return all
end
--根据掩码恢复数据
local restoreByMask = function (data, maskingKey)
    local oriData = ""
    local len = string.len(data)
    for index=1, len, 1 do
        local ch1 = string.sub(data, index, index)
        local maskIndex = (index-1)%4 + 1
        local ch2 = string.sub(maskingKey, maskIndex, maskIndex)
        oriData = oriData .. string.char(bit.bxor(string.byte(ch1),string.byte(ch2)))
    end
    return oriData
end
--阶段枚举定义
local Stage = {
    FIN_RSV = 1,
    MASK_PayloadLength = 2,
    ExtendedPayloadLength = 3,
    MaskingKey = 4,
    ExtensionData = 5,
    ApplicationData = 6
}
--固定长度的阶段定义
local FixedStageLen = {
    FIN_RSV = 1, --1个字节
    MASK_PayloadLength = 1, --1个字节
    MaskingKey = 4, --4个字节
}
local M = {}

M.new = function (extensionDataLength, onEvent)
    local prevData = ""
    local onFrame = function (frame)
        if 0 == frame.opcode then
            onEvent("continuation")
        elseif 1 == frame.opcode then
            prevData = prevData .. frame.ApplicationData
            if 1 == frame.FIN then
                onEvent("text", prevData)
                prevData = ""
            end
        elseif 2 == frame.opcode then
            prevData = prevData .. frame.ApplicationData
            if 1 == frame.FIN then
                onEvent("binary", prevData)
                prevData = ""
            end
        elseif 8 == frame.opcode then
            onEvent("close")
        elseif 9 == frame.opcode then
            onEvent("ping")
        elseif 10 == frame.opcode then
            onEvent("pong")
        else
        end
    end
    local frame
    local stage = Stage.FIN_RSV --初始化阶段
    local extract = function (trunk)
        if Stage.FIN_RSV == stage then
            local stageLen = FixedStageLen.FIN_RSV
            if stageLen <= string.len(trunk) then
                frame = {} --开始一个新对象
                local data = string.sub(trunk, 1, stageLen)
                frame.FIN = getBit(data, 1)
                frame.RSV1 = getBit(data, 2)
                frame.RSV2 = getBit(data, 3)
                frame.RSV3 = getBit(data, 4)
                frame.opcode = getBit(data, 5, 8)
                stage = Stage.MASK_PayloadLength
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        if Stage.MASK_PayloadLength == stage then
            local stageLen = FixedStageLen.MASK_PayloadLength
            if stageLen <= string.len(trunk) then
                local data = string.sub(trunk, 1, stageLen)
                frame.MASK = getBit(data, 1)
                frame.PayloadLength = getBit(data, 2, 8)
                if 127 == frame.PayloadLength then
                    stage = Stage.ExtendedPayloadLength --需要继续获取数据长度
                    frame.LenOfExtendedPayloadLength = 8
                elseif 126 == frame.PayloadLength then
                    stage = Stage.ExtendedPayloadLength --需要继续获取数据长度
                    frame.LenOfExtendedPayloadLength = 2
                else
                    stage = Stage.MaskingKey --到了掩码阶段
                    frame.LenOfExtendedPayloadLength = 0
                end
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        if Stage.ExtendedPayloadLength == stage then
            local stageLen = frame.LenOfExtendedPayloadLength
            if stageLen <= string.len(trunk) then
                local data = string.sub(trunk, 1, stageLen)
                frame.PayloadLength = strToInt(data)
                stage = Stage.MaskingKey
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        if Stage.MaskingKey == stage then
            local stageLen = FixedStageLen.MaskingKey
            if stageLen <= string.len(trunk) then
                frame.MaskingKey = string.sub(trunk, 1, stageLen)
                if 0 < extensionDataLength then
                    stage = Stage.ExtensionData
                    frame.ExtensionDataLength = extensionDataLength
                else
                    stage = Stage.ApplicationData
                    frame.ExtensionDataLength = 0
                end
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        if Stage.ExtensionData == stage then
            local stageLen = frame.ExtensionDataLength
            if stageLen <= string.len(trunk) then
                frame.ExtensionData = string.sub(trunk, 1, stageLen)
                if 1 == frame.MASK then
                    frame.ExtensionData = restoreByMask(frame.ExtensionData, frame.MaskingKey)
                end
                stage = Stage.ApplicationData
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        if Stage.ApplicationData == stage then
            local stageLen = frame.PayloadLength - frame.ExtensionDataLength
            if stageLen <= string.len(trunk) then
                frame.ApplicationData = string.sub(trunk, 1, stageLen)
                if 1 == frame.MASK then
                    frame.ApplicationData = restoreByMask(frame.ApplicationData, frame.MaskingKey)
                end
                stage = Stage.FIN_RSV --下一个数据帧循环
                onFrame(frame)
                return string.sub(trunk, stageLen+1)
            else
                return trunk
            end
        end
        return trunk
    end
    return StreamExtractor(extract)
end

local genLenBytes = function (data, mask)
    local baseBytes, extendBytes
    local dataLen = string.len(data)
    local lenBytes
    if 126 > dataLen then
        lenBytes = 1
        baseBytes = string.char(dataLen)
    elseif math.pow(2, 16) > dataLen then
        lenBytes = 2
        baseBytes = string.char(126)
    else
        lenBytes = 8
        baseBytes = string.char(127)
    end
    if mask then
        baseBytes = string.char(string.byte(baseBytes) + 128)
    end
    if 1 < lenBytes then
        local lenStr = intToStr(dataLen)
        local minLen = string.len(lenStr)
        extendBytes = string.rep(string.char(0), lenBytes - minLen) .. lenStr
    else
        extendBytes = ""
    end
    return baseBytes .. extendBytes
end

M.sendFrame = function (lowerSend, typ, data, isEnd)
    if "continuation" == typ then
        lowerSend(string.char(0x80) .. string.char(0))
    elseif "text" == typ then
        local firstByte = 0x01
        if isEnd then
            firstByte = firstByte + 128
        end
        local lenBytes = genLenBytes(data, false)
        lowerSend(string.char(firstByte) .. lenBytes .. data)
    elseif "binary" == typ then
        local firstByte = 0x02
        if isEnd then
            firstByte = firstByte + 128
        end
        lowerSend(string.char(firstByte) .. lenBytes .. data)
    elseif "close" == typ then
        lowerSend(string.char(0x88) .. string.char(0))
    elseif "ping" == typ then
        lowerSend(string.char(0x89) .. string.char(0))
    elseif "pong" == typ then
        lowerSend(string.char(0x8A) .. string.char(0))
    else
    end
end

M.sha1 = sha1

local moduleName = ...
_G[moduleName] = M
complex = M
return complex